<template>
  <div class="skybox">
    <div class="title">
      <el-radio-group>
        <el-radio-button value="white" @change="setLight('white')">白色灯光</el-radio-button>
        <el-radio-button value="green" @change="setLight('green')">绿色灯光</el-radio-button>
        <el-radio-button value="red" @change="setLight('red')">红色灯光</el-radio-button>
        <el-radio-button @click="toggleCamera">切换相机</el-radio-button>
      </el-radio-group>
    </div>
    <div ref="refDom" class="model-view">
    </div>
  </div>
</template>

<script setup>
import {useTemplateRef} from 'vue'
import * as THREE from 'three'
import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls.js'
import {GLTFLoader} from 'three/addons/loaders/GLTFLoader.js'
import gsap from 'gsap'
import {GUI} from 'three/addons/libs/lil-gui.module.min.js'
import Stats from 'three/examples/jsm/libs/stats.module.js'

defineOptions({
  name: 'threejs'
})
const refDom = useTemplateRef('refDom')
let scene, camera, camera1, controls, renderer
const light = new THREE.DirectionalLight()
let group = null
let su7 = null
onMounted(() => {
  init()
})
let cameraBol = true
const toggleCamera = () => {
  cameraBol = !cameraBol
}
const setLight = (type) => {
  const colors = {
    'white': 'rgb(255,255,255)',
    'green': 'rgb(0,255,0)',
    'red': 'rgb(255,0,0)'
  }
  // 光线
  scene.remove(light)
  light.color = new THREE.Color(colors[type])
  light.intensity = 50 // 光线强度
  light.position.set(...targetPosition)
  scene.add(light)
}

const raycaster = new THREE.Raycaster() // 射线
const targetPosition = new THREE.Vector3() // 目标位置
const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0) // 碰撞面
let oldGsap = null // gsap动画对象

const init = async () => {
  scene = new THREE.Scene()
  const DOM = refDom.value

// 创建性能监视器
  let stats = new Stats()
// 设置监视器面板，传入面板id（0: fps, 1: ms, 2: mb）
  stats.setMode(0)
// 设置监视器位置
  stats.domElement.style.position = 'absolute'
  stats.domElement.style.left = '0px'
  stats.domElement.style.top = '0px'
// 将监视器添加到页面中
  DOM.appendChild(stats.domElement)

// 透视摄像机
  camera = new THREE.PerspectiveCamera(75, DOM.clientWidth / DOM.clientHeight, 0.1, 1000)
  camera.position.set(0, 5, -10)
  camera.lookAt(0, 1, 0)

  camera1 = new THREE.PerspectiveCamera(8, DOM.clientWidth / DOM.clientHeight, 15, 25)
  camera1.position.set(0, 5, -20)
  camera1.lookAt(0, 1, 0)
  const gui = new GUI()
  gui.add(camera1, 'fov', 1, 180).onChange(updateCamera)
  gui.add(camera1, 'near', 1, 200).onChange(updateCamera)
  gui.add(camera1, 'far', 1, 200).onChange(updateCamera)
// 设置监视器位置
  gui.domElement.style.position = 'absolute'
  gui.domElement.style.right = '0px'
  gui.domElement.style.top = '0px'
  DOM.appendChild(gui.domElement)

  const cameraHelper = new THREE.CameraHelper(camera1)
// 辅助线加入 场景
  scene.add(cameraHelper)
  renderer = new THREE.WebGLRenderer({
    antialias: true, // 抗锯齿
    alpha: true//背景色透明
  })
  renderer.setPixelRatio(window.devicePixelRatio)
  renderer.toneMapping = THREE.ACESFilmicToneMapping
  renderer.toneMappingExposure = 1
  renderer.setSize(DOM.clientWidth, DOM.clientHeight)
  renderer.shadowMap.enabled = true
  DOM.appendChild(renderer.domElement)

  // 控制器
  controls = new OrbitControls(camera, renderer.domElement) // 鼠标控制
  controls.enableDamping = true // 设置阻尼
  controls.minDistance = 1
  controls.maxDistance = 100
  // controls.target.set(0, 0, -0.2)
  // controls.update()

  //加载模型
  const gltfLoader = new GLTFLoader()
  gltfLoader.load(`${import.meta.env.VITE_THREEJS_BASE_URL}/soldier/soldier.glb`, (gltf) => {
    group = gltf.scene
    group.position.set(0, 0.5, 0)
    scene.add(group)
    const clock = new THREE.Clock() // 时钟
    const mixer = new THREE.AnimationMixer(group) // 模型动画
    group.mixerAnimateRender = () => mixer.update(clock.getDelta()) // 动画帧
    group.currentAction = mixer.clipAction(gltf.animations[3]) // walk 动画
  })

  gltfLoader.load(`${import.meta.env.VITE_THREEJS_BASE_URL}/su7/scene.gltf`, (gltf) => {
    su7 = gltf.scene
    su7.rotation.y = 1.55
    su7.position.set(0, 0, 5)
    scene.add(su7)
  })

  const rectGeo = new THREE.BoxGeometry(2, 1, 1)
  const rectMater = new THREE.MeshLambertMaterial({
    color: 0xffd700
  })
  const rectMesh = new THREE.Mesh(rectGeo, rectMater)
  scene.add(rectMesh)

  window.onresize = () => {
    renderer.setSize(DOM.clientWidth, DOM.clientHeight)
    camera.aspect = DOM.clientWidth / DOM.clientHeight
    camera.updateProjectionMatrix()
  }

  scene.add(new THREE.AxesHelper(100)) // xyz轴辅助线
  // scene.add(new THREE.GridHelper(50, 100, 0x135f9e, 0x135f9e)) // 地板网格辅助线
  scene.add(new THREE.AmbientLight(0xFFFFFF, 0.01)) // 环境光


  const directionalLight = new THREE.DirectionalLight(0xffffff, 1) // 平行光
  directionalLight.position.set(0, 5, -2)
  directionalLight.target = rectMesh
  scene.add(directionalLight)
  /*const dirLightHelper = new THREE.DirectionalLightHelper(directionalLight, 5, 0xff0000)
   scene.add(dirLightHelper)*/

  const spotLight = new THREE.SpotLight(0xff0000, 100) // 聚灯光
  const lightGeo = new THREE.SphereGeometry(0.1, 20, 20) // 头顶灯泡球形几何体
  const lightMater = new THREE.MeshBasicMaterial({color: 0xff0000}) // 聚光灯泡材质（基础网格材质）
  // lightMater.transparent = true
  // lightMater.opacity = 0.5
  spotLight.add(new THREE.Mesh(lightGeo, lightMater))
  spotLight.position.set(0, 8, 0)
  // spotLight.angle = Math.PI / 5
  spotLight.penumbra = 1
  spotLight.decay = 2
  spotLight.distance = 0
  spotLight.castShadow = true
  spotLight.shadow.mapSize.width = 1024
  spotLight.shadow.mapSize.height = 1024
  spotLight.shadow.camera.near = 1
  spotLight.shadow.camera.far = 10
  spotLight.shadow.focus = 1
  // scene.add(spotLight) // 点光

  // 地面 平铺
  {
    const planeSize = 50
    const loader = new THREE.TextureLoader()
    const texture = loader.load(`${import.meta.env.VITE_THREEJS_BASE_URL}/images/checker.png`)
    // THREE.RepeatWrapping 纹理重复
    texture.wrapS = THREE.RepeatWrapping
    texture.wrapT = THREE.RepeatWrapping
    // 当一个纹素覆盖大于一个像素时，贴图将如何采样。 THREE.NearestFilter，它将使用最接近的纹素的值。
    texture.magFilter = THREE.NearestFilter
    const repeats = planeSize
    // 重复次数
    texture.repeat.set(repeats, repeats)
    const planeGeo = new THREE.PlaneGeometry(planeSize, planeSize)
    // Phong材质
    const planeMat = new THREE.MeshPhongMaterial({
      map: texture,
      side: THREE.DoubleSide
    })
    const mesh = new THREE.Mesh(planeGeo, planeMat)
    mesh.rotation.x = Math.PI * -0.5
    scene.add(mesh)
  }

  const render = (time) => {
    time *= 0.0005
    // 更新帧数
    stats.update()
    cameraHelper.update()
    spotLight.position.z = Math.sin(time) * 5 // 聚光灯
    // group?.rotateY(0.01)//每次绕y轴旋转0.02弧度
    renderer.render(scene, cameraBol ? camera : camera1)
    requestAnimationFrame(render)
  }
  render()
  // 点击事件
  DOM.addEventListener('contextmenu', (event) => {
    const mouse = new THREE.Vector2((event.offsetX / event.target.clientWidth) * 2 - 1, -(event.offsetY / event.target.clientHeight) * 2 + 1)
    raycaster.setFromCamera(mouse, camera)
    raycaster.ray.intersectPlane(plane, targetPosition)
    const mesh = new THREE.Mesh(new THREE.BoxGeometry(0.05, 0.05, 0.05), new THREE.MeshBasicMaterial())
    mesh.position.copy(targetPosition)
    scene.add(mesh)
    goAddress(group, targetPosition)
  })
}

const updateCamera = () => {
  camera1.updateProjectionMatrix()
}

const goAddress = (group, targetPosition) => {
  oldGsap?.kill() // 停止上一个动画
  const distance = group.position.distanceTo(targetPosition) // 距离
  const vector = camera.position.clone().sub(group.position) // camera 和 group 差向量
  group.lookAt(targetPosition) // 模型朝向
  group.rotation.y += Math.PI  // 朝向纠正
  const duration = distance / 2  // 距离 / 速度
  oldGsap = gsap.to(group.position, {
    ...targetPosition, duration, ease: 'none',
    onStart: () => {
      // controls.enabled = false // 禁止控制
      group.currentAction.play() // 播放动画
    },
    onUpdate: () => {
      group.mixerAnimateRender() // 动画帧
      // controls.target.copy(group.position) // 目标跟随
      camera.position.lerp(group.position.clone().add(vector), 0.1) // 相机跟随
    },
    onComplete: () => controls.enabled = true // 恢复控制
  })

}

</script>

<style lang="scss" scoped>
.skybox {
  width: 100%;
  height: 100%;

  .title {
    height: 30px;
    line-height: 30px;
    margin-bottom: 10px;
  }

  .model-view {
    position: relative;
    width: 100%;
    height: calc(100% - 40px);
    //background: #1f1f1f;
  }
}
</style>
